package com.xxkfz.aspect.lock;

import cn.hutool.core.util.StrUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @program: xxkfz-admin-redisson
 * @ClassName RedissonLockAspect.java
 * @author: 公众号：小小开发者
 * @create: 2024-01-20 16:15
 * @description:
 * @Version 1.0
 **/
@Aspect
@RequiredArgsConstructor
@Slf4j
@Component
public class RedissonLockAspect {

    private final RedissonClient redissonClient;


    /**
     * 环绕切面
     *
     * @param joinPoint
     * @param redissonLock
     * @return
     * @throws Throwable
     */
    @Around("@annotation(redissonLock)")
    public Object around(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) throws Throwable {
        String lockKey = this.getLockKey(joinPoint, redissonLock);
        int leaseTime = redissonLock.leaseTime();
        String errMsg = redissonLock.errMsg();
        int waitTime = redissonLock.waitTime();
        TimeUnit timeUnit = redissonLock.timeUnit();
        RLock rLock = redissonClient.getLock(lockKey);

        Object res;
        try {
            boolean tryLock = rLock.tryLock(waitTime, leaseTime, timeUnit);
            // 获取锁失败
            if (!tryLock) {
                throw new RuntimeException(errMsg);
            }
            log.error("获取到锁,lockName = {}", lockKey);
            res = joinPoint.proceed();
        } catch (Exception ex) {
            log.error("执行业务方法异常:{}", ex);
            throw ex;
        } finally {
            if (rLock.isHeldByCurrentThread()) {
                rLock.unlock();
            }
        }

        return res;
    }

    /**
     * 解析El表达式获取lockKey
     *
     * @param joinPoint
     * @param redissonLock
     * @return
     */
    private String getLockKey(ProceedingJoinPoint joinPoint, RedissonLock redissonLock) {
        String key = redissonLock.lockKey();
        Object[] parameterValues = joinPoint.getArgs();
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();
        DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
        String[] parameterNames = nameDiscoverer.getParameterNames(method);
        // 没有指定key
        if (StrUtil.isEmpty(key)) {
            if (parameterNames != null && parameterNames.length > 0) {
                StringBuffer sb = new StringBuffer();
                int i = 0;

                for (int len = parameterNames.length; i < len; ++i) {
                    sb.append(parameterNames[i]).append(" = ").append(parameterValues[i]);
                }

                key = sb.toString();
            } else {
                key = methodName;
            }

            return key;
        } else {
            SpelExpressionParser parser = new SpelExpressionParser();
            Expression expression = parser.parseExpression(key);
            if (parameterNames != null && parameterNames.length != 0) {
                EvaluationContext evaluationContext = new StandardEvaluationContext();

                for (int i = 0; i < parameterNames.length; ++i) {
                    evaluationContext.setVariable(parameterNames[i], parameterValues[i]);
                }
                try {
                    Object expressionValue = expression.getValue(evaluationContext);
                    return expressionValue != null && !"".equals(expressionValue.toString()) ? expressionValue.toString() : key;
                } catch (Exception ex) {
                    return key;
                }
            } else {
                return key;
            }
        }
    }

}